#!/usr/bin/python
#coding=utf-8

import pandas as pd
import xgboost as xgb
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
from xgboost import plot_importance, XGBClassifier
import os

"""
参考程序：https://juejin.im/post/5a1bb29e51882531ba10aa49
官方API文档：http://xgboost.readthedocs.io/en/latest/python/python_api.html#module-xgboost.training
"""

g_gbm = None

"""
train_data：所要划分的样本特征集
train_target：被划分的样本标签, 就是train_data每行对应的标识，label_columnName列内容的来源
test_size：样本占比，如果是整数的话就是样本的数量
random_state：是随机数的种子。

label_columnName: 训练集中目标列名, 是我们的训练目标
"""
def ad_xgboost_train(train_data, train_target, label_columnName, gbm_name='AD_xgboost.model', test_size=0.2, feature_columns_to_use=[]):
    # 避免过拟合，采用交叉验证，验证集占训练集20%，固定随机种子（random_state)
    train_x, test_x, train_y, test_y = train_test_split(train_data,
                                                       train_target,
                                                       test_size=test_size,
                                                       random_state=0)
    if {'time'}.issubset(train_data.columns):
        train_x['time'] = pd.to_datetime(pd.Series(train_x['time']))
        test_x['time'] = pd.to_datetime(pd.Series(test_x['time']))

        train_x['Year'] = train_x['time'].apply(lambda x:x.year)
        train_x['Month'] = train_x['time'].apply(lambda x: x.month)
        train_x['weekday'] = train_x['time'].apply(lambda x: x.weekday())
        train_x['time'] = train_x['time'].dt.time
        train_x = train_x.drop('time', axis=1)
        train_x = train_x.dropna(axis=0)

        test_x['Year'] = test_x['time'].apply(lambda x: x.year)
        test_x['Month'] = test_x['time'].apply(lambda x: x.month)
        test_x['weekday'] = test_x['time'].dt.dayofweek
        test_x['time'] = test_x['time'].dt.time
        test_x = test_x.drop('time', axis=1)

    test_x = test_x.fillna(method='pad')

    for f in train_x.columns:
        if train_x[f].dtype=='object':
            if f != label_columnName:
                print(f)
                lbl = preprocessing.LabelEncoder()
                train_x.loc[:,f] = lbl.fit_transform(list(train_x[f].values))
                '''
                #####特征向量化############
                from sklearn.feature_extraction import DictVectorizer
                dict_vec = DictVectorizer(sparse=False)

                ###########归一化和标准化#################
                train_x[f] = dict_vec.fit_transform(train_x[f].to_dict(orient='record'))
                '''

    for f in test_x.columns:
        if test_x[f].dtype == 'object':
            print(f)
            lbl = preprocessing.LabelEncoder()
            lbl.fit(list(test_x[f].values))
            test_x[f] = lbl.transform(list(test_x[f].values))

    if feature_columns_to_use:
        big_train = train_x[feature_columns_to_use]
        big_test = test_x[feature_columns_to_use]
        train_X = big_train.values
        test_X = big_test.values
    else:
        train_X = train_x.values
        test_X = test_x.values

    """
    xgboost.XGBClassifier 分类算法 参数详解 https://blog.csdn.net/qq_36603091/article/details/80592157
    Booster     gbtree 树模型做为基分类器（默认）
                gbliner 线性模型做为基分类器
    n_jobs      并行线程数
    silent
                silent=0时，不输出中间过程（默认）
                silent=1时，输出中间过程
    nthread
                nthread=-1时，使用全部CPU进行并行运算（默认）
                nthread=1时，使用1个CPU进行运算。
    scale_pos_weight
                正样本的权重，在二分类任务中，当正负样本比例失衡时，设置正样本的权重，模型效果更好。例如，
                当正负样本比例为1:10时，scale_pos_weight=10。
    n_estimators
                也就是弱学习器的最大迭代次数，或者说最大的弱学习器的个数。一般来说n_estimators太小，容易欠拟合，
                n_estimators太大，计算量会太大，并且n_estimators到一定的数量后，再增大n_estimators获得的模型提升会很小，
                所以一般选择一个适中的数值。默认是100。
                含义：总共迭代的次数，即决策树的个数
                调参：
    max_depth
                含义：树的深度，默认值为6，典型值3-10。
                调参：值越大，越容易过拟合；值越小，越容易欠拟合。
    min_child_weight
                含义：默认值为1,。
                调参：值越大，越容易欠拟合；值越小，越容易过拟合（值较大时，避免模型学习到局部的特殊样本）。
    subsample
                含义：训练每棵树时，使用的数据占全部训练集的比例。默认值为1，典型值为0.5-1。
                调参：防止overfitting。
    colsample_bytree
                含义：训练每棵树时，使用的特征占全部特征的比例。默认值为1，典型值为0.5-1。
                调参：防止overfitting。
    learning_rate
                含义：学习率，控制每次迭代更新权重时的步长，默认0.3。
                调参：值越小，训练越慢。
                典型值为0.01-0.2。
    gamma
                惩罚项系数，指定节点分裂所需的最小损失函数下降值。
                调参：
    alpha
                L1正则化系数，默认为1
    lambda
                L2正则化系数，默认为1
    """
    # 建立模型
    clf_gbm = XGBClassifier(silent = 1,
                        learning_rate = 0.1,
                        n_estimators = 1000,            # 树的个数--1000棵树建立xgboost
                        max_depth = 6,                  # 树的深度
                        min_child_weight = 1,           # 叶子节点最小权重
                        gamma = 0.,                     # 惩罚项中叶子结点个数前的参数
                        subsample = 0.8,                # 随机选择80%样本建立决策树
                        colsample_btree = 0.8,          # 随机选择80%特征建立决策树
                        objective = 'multi:softmax',    # 指定损失函数
                        scale_pos_weight = 1,           # 解决样本个数不平衡的问题
                        random_state = 27               # 随机数
                    )

    ### 模型训练, fit model for train data
    clf_gbm.fit(train_x,
            train_y,
            eval_set = [(test_x, test_y)],              # 评估数据集,list类型
            eval_metric = "mlogloss",                   # 评估标准(多分类问题，使用mlogloss作为损失函数)
            early_stopping_rounds = 10,                 # 如果模型的loss十次内没有减小，则提前结束模型训练
            verbose = True)                             # True显示，False不显示

    path = os.path.abspath(__file__)
    # 保存训练结果
    if gbm_name:
        clf_gbm.save_model(path[0:path.index('Libs')]+'/Datas/AD_xgboost/'+gbm_name)

    ### 模型预测, make prediction for test data
    pred_y = clf_gbm.predict(test_x)

    ### 性能度量, model evaluate
    accuracy = accuracy_score(test_y, pred_y)
    print("accuarcy: %.2f%%" % (accuracy*100.0))

    return clf_gbm


def ad_xgboost_predict(test_data, gbm_name='AD_xgboost.model'):
    path = os.path.abspath(__file__)
    if gbm_name:
        gbm = xgb.Booster(model_file = path[0:path.index('Libs')]+'/Datas/AD_xgboost/'+gbm_name)
    else:
        if (g_gbm == None):
            g_gbm = xgb.Booster(model_file = path[0:path.index('Libs')]+'/Datas/AD_xgboost/AD_xgboost.model')
        gbm = g_gbm

    # predict返回的是一个大小为n的一维数组，一维数组中的第i个值为模型预测第i个预测样本的标签
    y_pre = gbm.predict(test_data)

    # predict_proba返回的是一个n行k列的数组，第i行第j列上的数值是模型预测第i个预测样本为某个标签的概率，并且每一行的概率和为1。
    y_pro = gbm.predict_proba(test_data)[:, 1]
    #print "AUC Score : %f" % metrics.roc_auc_score(test_label, y_pro)
    #print"Accuracy : %.4g" % metrics.accuracy_score(test_label, y_pre)

    return y_pre, y_pro


def ad_xgboost_importance(importance='', gbm_name='AD_xgboost.model'):
    ### plot feature importance
    fig, ax = plt.subplots(figsize = (15, 15))

    if importance:
        plot_importance(importance,
                        height = 0.5,
                        ax = ax,
                        max_num_features = 64)
    else:
        path = os.path.abspath(__file__)
        if gbm_name:
            gbm = xgb.Booster(model_file = path[0:path.index('Libs')]+'/Datas/AD_xgboost/'+gbm_name)
        else:
            if (g_gbm == None):
                g_gbm = xgb.Booster(model_file = path[0:path.index('Libs')]+'/Datas/AD_xgboost/AD_xgboost.model')
            gbm = g_gbm

        plot_importance(gbm.get_fscore(),
                        height = 0.5,
                        ax = ax,
                        max_num_features = 64)

    plt.show()
